#include <stdio.h>
#include <string.h>
#include "defines.h"
#include "typelist.h"

FILE *file;


/* This builds a test for argument passing and returning of scalar types.  */

/* Maximum number of arguments.  */
#define MAXIMUM_ARGS   24

struct TestSetting
{
  int reg_count;   /* Amount of registers for argument passing.  */
  char reg_prefix; /* Prefix used in the argument passing sequence in args.h */
  char arg_prefix; /* Prefix for functions, macros and the Register struct.  */
  char string[20]; /* "int" or "float".  */
};
struct TestSetting test_setting[3];

#define INT_TEST   0
#define FLOAT_TEST 1
#define X87_TEST 2

void
init_test_settings ()
{
  test_setting[INT_TEST].reg_count = 6;
  test_setting[INT_TEST].reg_prefix = 'I';
  test_setting[INT_TEST].arg_prefix = 'i';
  strcpy(test_setting[INT_TEST].string, "int");

  test_setting[FLOAT_TEST].reg_count = 8;
  test_setting[FLOAT_TEST].reg_prefix = 'F';
  test_setting[FLOAT_TEST].arg_prefix = 'f';
  strcpy(test_setting[FLOAT_TEST].string, "float");

  test_setting[X87_TEST].reg_count = 0;
  test_setting[X87_TEST].reg_prefix = 'F';
  test_setting[X87_TEST].arg_prefix = 'f';
  strcpy(test_setting[X87_TEST].string, "x87");
}


void
open_file (char *filename, int use_ints, int use_floats)
{
  int i;

  file = fopen (filename, "w");
  if (!file)
    {
      fprintf (stderr, "Couldn't open file for writing\n");
      exit (1);
    }

  /* Make top of the file.  */
  fprintf (file, "/* This is an autogenerated file. Do not edit.  */\n\n");
  fprintf (file, "#include \"defines.h\"\n");
  fprintf (file, "#include \"macros.h\"\n");
  fprintf (file, "#include \"args.h\"\n\n");

  fprintf (file, "struct IntegerRegisters iregs;\n");
  fprintf (file, "struct FloatRegisters fregs;\n");
  fprintf (file, "unsigned int num_iregs, num_fregs;\n\n");

  /* Make the struct for checking the parameter values.  */
  fprintf (file, "/* This struct holds values for argument checking.  */\n");
  if (use_ints)
    {
      const char *types[] = { "int", "long",
#if defined (CHECK_LARGE_SCALAR_PASSING) && defined (CHECK_INT128)
	"__int128",
#endif
	0 };
      const char **t;
      for (t = types; *t; t++)
	{
	  fprintf (file, "struct \n{\n");
          fprintf (file, "  %s i0", *t);
	  for (i=1; i<MAXIMUM_ARGS; i++)
	    fprintf (file, ", i%d", i);
	  fprintf (file, ";\n");
	  fprintf (file, "} values_%s;\n\n", *t);
	}
    }
  if (use_floats)
    {
      const char *types[] = { "float", "double",
#ifdef CHECK_LARGE_SCALAR_PASSING
#ifdef CHECK_FLOAT128
	"__float128",
#endif
#ifdef CHECK_LONG_DOUBLE
	"ldouble",
#endif
#endif
	0 };
      const char **t;
      for (t = types; *t; t++)
	{
	  fprintf (file, "struct \n{\n");
          fprintf (file, "  %s f0", *t);
	  for (i=1; i<MAXIMUM_ARGS; i++)
	    fprintf (file, ", f%d", i);
	  fprintf (file, ";\n");
	  fprintf (file, "} values_%s;\n\n", *t);
	}
    }
}


/* Construct the two test functions for testing argument passing.  */
void
make_checking_function (int test_type, char *type, int arg_count)
{
  int i;
  char arg_prefix = test_setting[test_type].arg_prefix;
  char *type_str = test_setting[test_type].string;

  /* Values test.  */
  fprintf (file, "void\nfun_check_%s_passing_%s%d_values (", type_str, type,
	  arg_count);
  for (i=0 ; i<arg_count; i++) {
    if (i != 0)
      fprintf (file, ", ");
    fprintf (file, "%s %c%d ATTRIBUTE_UNUSED", type, arg_prefix, i);
  }
  fprintf (file, ")\n{\n");
  fprintf (file, "  /* Check argument values.  */\n");
  for (i=0; i<arg_count; i++)
    fprintf (file, "  assert (values_%s.%c%i == %c%i);\n", type, arg_prefix, i, arg_prefix, i);
  fprintf (file, "\n}\n\n");

  /* Registers test.  */
  fprintf (file, "void\nfun_check_%s_passing_%s%d_regs (", type_str, type, arg_count);
  for (i=0 ; i<arg_count; i++) {
    if (i != 0)
      fprintf (file, ", ");
    fprintf (file, "%s %c%d ATTRIBUTE_UNUSED", type, arg_prefix, i);
  }
  fprintf (file, ")\n{\n");
  fprintf (file, "  /* Check register contents.  */\n");
  fprintf (file, "  check_%s_arguments;\n}\n\n", type);
}


/* Construct a macro used to test argument passing.  */
void
make_checking_define (int test_type, int arg_count, int for_int128)
{
  int i, regnum;
  int max_regs = test_setting[test_type].reg_count;
  char arg_prefix = test_setting[test_type].arg_prefix;
  char reg_prefix = test_setting[test_type].reg_prefix;
  char *type_str = test_setting[test_type].string;

  /* Print define header.  */
  fprintf (file, "#define def_check_%s_passing%d%s(", type_str, arg_count,
    for_int128 ? "_128": "");
  for (i=0; i<arg_count; i++)
    fprintf (file, "_%c%d, ", arg_prefix, i);
  fprintf (file, "_func1, _func2, TYPE) \\\n");

  /* Test actual parameters' values, also for those passed on the stack.  */
  for (i=0; i<arg_count; i++)
    fprintf (file, "  values_ ## TYPE .%c%d = _%c%d; \\\n", arg_prefix, i, arg_prefix, i);
  fprintf (file, "  WRAP_CALL(_func1) (");
  for (i=0; i<arg_count; i++)
    {
      if (i != 0)
	fprintf (file, ", ");
      fprintf (file, "_%c%d", arg_prefix, i);
    }
  fprintf (file, "); \\\n  \\\n");

  /* Test values passed in registers.  */
  fprintf (file, "  clear_%s_registers; \\\n", type_str);
  for (i=0, regnum=0; i<arg_count && regnum<max_regs; i++, regnum++)
    if (arg_prefix == 'i')
      {
	if (!for_int128)
          fprintf (file, "  %cregs.%c%d = _%c%d; \\\n", arg_prefix, reg_prefix, regnum,
	           arg_prefix, i);
        else if (i < max_regs)
	  {
	    fprintf (file, "  %cregs.%c%d = (unsigned long) _%c%d; \\\n", arg_prefix, reg_prefix, regnum, arg_prefix, i);
	    fprintf (file, "  %cregs.%c%d = _%c%d >> 64; \\\n", arg_prefix, reg_prefix, regnum+1, arg_prefix, i);
	    regnum++;
	  }
      }
    else
      fprintf (file, "  %cregs.%c%d._ ## TYPE [0] = _%c%d; \\\n", arg_prefix, reg_prefix, regnum,
	       arg_prefix, i);
  fprintf (file, "  num_%cregs = %d; \\\n", arg_prefix, regnum);

  /* Make function call.  */
  fprintf (file, "  WRAP_CALL(_func2) (");
  for (i=0; i<arg_count; i++)
    {
      if (i != 0)
	fprintf (file, ", ");
      fprintf (file, "_%c%d", arg_prefix, i);
    }
  fprintf (file, ");\n\n");
}


void
make_test_defs (int use_ints, int use_floats)
{
  /* Make defines for integer-type functions.  */
  if (use_ints)
    {
      make_checking_define (INT_TEST, 6, 0);
      make_checking_define (INT_TEST, 12, 0);
#if defined (CHECK_LARGE_SCALAR_PASSING) && defined (CHECK_INT128)
      make_checking_define (INT_TEST, 3, 1);
      make_checking_define (INT_TEST, 10, 1);
#endif
    }

  /* Make defines for floating-type point functions.  */
  if (use_floats)
    {
      make_checking_define (FLOAT_TEST, 8, 0);
      make_checking_define (FLOAT_TEST, 16, 0);
      make_checking_define (FLOAT_TEST, 20, 0);
      make_checking_define (X87_TEST, 8, 0);
      make_checking_define (X87_TEST, 16, 0);
      make_checking_define (X87_TEST, 20, 0);
    }
}


/* This makes a call to the defined macro with arg_count arguments. The
   arguments values are a 32, 33, 34...  */
void
make_test (char *name, int test_type, char *type, int arg_count)
{
  int i;
  char *type_str = test_setting[test_type].string;

  fprintf (file, "void\n%s ()\n{\n", name);
  fprintf (file, "  def_check_%s_passing%d(", type_str, arg_count);
  for (i=0; i<arg_count; i++)
    fprintf (file, "%d, ", 32+i);
  fprintf (file, "fun_check_%s_passing_%s%d_values, ", type_str, type, arg_count);
  fprintf (file, "fun_check_%s_passing_%s%d_regs, %s);\n}\n\n", type_str, type,
	  arg_count, type);
}

void
make_test_int128 (char *name, int test_type, char *type, int arg_count)
{
  int i;
  char *type_str = test_setting[test_type].string;

  fprintf (file, "void\n%s ()\n{\n", name);
  for (i=0; i<arg_count; i++)
    fprintf (file, "  %s _local%d = %d;\n", type, i, 32+i);
  for (i=0; i<arg_count; i++)
    fprintf (file, "  _local%d = _local%d | (_local%d << 64);\n", i, i, i);
  fprintf (file, "  def_check_%s_passing%d_128(", type_str, arg_count);
  for (i=0; i<arg_count; i++)
    fprintf (file, "_local%d, ", i);
  fprintf (file, "fun_check_%s_passing_%s%d_values, ", type_str, type, arg_count);
  fprintf (file, "fun_check_%s_passing_%s%d_regs, %s);\n}\n\n", type_str, type,
	  arg_count, type);
}


/* Make two tests in one function.  */
void
make_test2 (char *name, int test_type, char *type, int count1, int count2)
{
  int i;
  char *type_str = test_setting[test_type].string;

  fprintf (file, "void\n%s ()\n{\n", name);

  fprintf (file, "  def_check_%s_passing%d(", type_str, count1);
  for (i=0; i<count1; i++)
    fprintf (file, "%d, ", 32+i);
  fprintf (file, "fun_check_%s_passing_%s%d_values, ", type_str, type, count1);
  fprintf (file, "fun_check_%s_passing_%s%d_regs, %s);\n\n", type_str, type, count1, type);

  fprintf (file, "  def_check_%s_passing%d(", type_str, count2);
  for (i=0; i<count2; i++)
    fprintf (file, "%d, ", 32+i);
  fprintf (file, "fun_check_%s_passing_%s%d_values, ", type_str, type, count2);
  fprintf (file, "fun_check_%s_passing_%s%d_regs, %s);\n}\n\n", type_str, type, count2, type);
}


/* This outputs an empty function. Used by disabled tests.  */
void
make_empty_tests (char *type)
{
  fprintf (file, "void\ntest_%ss_on_stack ()\n{\n}\n\n", type);
  fprintf (file, "void\ntest_too_many_%ss ()\n{\n}\n\n", type);
}


void
make_int_tests ()
{
  open_file ("test_passing_integers.c", 1, 0);

  make_checking_function (INT_TEST, "int", 6);
  make_checking_function (INT_TEST, "int", 12);
  make_checking_function (INT_TEST, "long", 6);
  make_checking_function (INT_TEST, "long", 12);
#if defined (CHECK_LARGE_SCALAR_PASSING) && defined (CHECK_INT128)
  make_checking_function (INT_TEST, "__int128", 3);
  make_checking_function (INT_TEST, "__int128", 10);
#endif

  make_test_defs (1, 0);

  make_test ("test_ints_on_stack", INT_TEST, "int", 6);
  make_test ("test_too_many_ints", INT_TEST, "int", 12);

  make_test ("test_longs_on_stack", INT_TEST, "long", 6);
  make_test ("test_too_many_longs", INT_TEST, "long", 12);

#if defined (CHECK_LARGE_SCALAR_PASSING) && defined (CHECK_INT128)
  make_test_int128 ("test_int128s_on_stack", INT_TEST, "__int128", 3);
  make_test_int128 ("test_too_many_int128s", INT_TEST, "__int128", 10);
#else
  make_empty_tests ("int128");
#endif

  fprintf (file, "\nint\nmain (void)\n{\n");
  fprintf (file, "  test_ints_on_stack ();\n");
  fprintf (file, "  test_too_many_ints ();\n");
  fprintf (file, "  test_longs_on_stack ();\n");
  fprintf (file, "  test_too_many_longs ();\n");
  fprintf (file, "  test_int128s_on_stack ();\n");
  fprintf (file, "  test_too_many_int128s ();\n");
  fprintf (file, "  return 0;\n}\n");

  fclose (file);
}

void
make_float_tests ()
{
  open_file ("test_passing_floats.c", 0, 1);

  make_checking_function (FLOAT_TEST, "float", 8);
  make_checking_function (FLOAT_TEST, "float", 16);
  make_checking_function (FLOAT_TEST, "float", 20);
  make_checking_function (FLOAT_TEST, "double", 8);
  make_checking_function (FLOAT_TEST, "double", 16);
  make_checking_function (FLOAT_TEST, "double", 20);
#ifdef CHECK_LARGE_SCALAR_PASSING
#ifdef CHECK_FLOAT128
  make_checking_function (FLOAT_TEST, "__float128", 4);
  make_checking_function (FLOAT_TEST, "__float128", 8);
  make_checking_function (FLOAT_TEST, "__float128", 10);
#endif
#ifdef CHECK_LONG_DOUBLE
  make_checking_function (X87_TEST, "ldouble", 8);
  make_checking_function (X87_TEST, "ldouble", 16);
  make_checking_function (X87_TEST, "ldouble", 20);
#endif
#endif

  make_test_defs (0, 1);

#ifdef CHECK_FLOAT_DOUBLE_PASSING
  make_test2 ("test_floats_on_stack", FLOAT_TEST, "float", 8, 16);
  make_test ("test_too_many_floats", FLOAT_TEST, "float", 20);

  make_test2 ("test_doubles_on_stack", FLOAT_TEST, "double", 8, 16);
  make_test ("test_too_many_doubles", FLOAT_TEST, "double", 20);
#else
  make_empty_tests ("float");
  make_empty_tests ("double");
#endif

#if defined (CHECK_LARGE_SCALAR_PASSING) && defined (CHECK_LONG_DOUBLE)
  make_test ("test_long_doubles_on_stack", X87_TEST, "ldouble", 8);
  make_test ("test_too_many_long_doubles", X87_TEST, "ldouble", 20);
#else
  make_empty_tests ("long_double");
#endif

#if defined (CHECK_LARGE_SCALAR_PASSING) && defined (CHECK_FLOAT128)
  make_test ("test_float128s_on_stack", FLOAT_TEST, "__float128", 8);
  make_test ("test_too_many_float128s", FLOAT_TEST, "__float128", 20);
#else
  make_empty_tests ("float128");
#endif

  fprintf (file, "\nint\nmain (void)\n{\n");
  fprintf (file, "  test_floats_on_stack ();\n");
  fprintf (file, "  test_too_many_floats ();\n");

  fprintf (file, "  test_doubles_on_stack ();\n");
  fprintf (file, "  test_too_many_doubles ();\n");

  fprintf (file, "  test_long_doubles_on_stack ();\n");
  fprintf (file, "  test_too_many_long_doubles ();\n");

  fprintf (file, "  test_float128s_on_stack ();\n");
  fprintf (file, "  test_too_many_float128s ();\n");
  fprintf (file, "  return 0;\n}\n");

  fclose (file);
}


/* Add tests for both integer and floating point arguments.  */
void
make_mixed_tests ()
{
  /* TODO: Make these tests in a separate file:  */
#if 0
  test_mixed_integers_on_stack ();
  test_too_many_mixed_integers ();
#endif
}


/* Construct the file.  */
int
main (int argc, char **argv)
{
  init_test_settings ();
  init_typelist ();

  make_int_tests ();
  make_float_tests ();
  make_mixed_tests ();

  return 0;
}
